package editor.modelo;

import java.awt.geom.Point2D;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.util.ArrayList;

import java.util.LinkedList;

import javax.swing.JFileChooser;
import javax.swing.JOptionPane;


import editor.modelo.Portal.Clase;
import editor.modelo.Portal.Tipo;


public class ModelUtilities {
	

	private static final int sq_distance_Vertical = 100; //para matchear los portales ext verticales
	private static final int distance_horiz = 10; //para matchear los portales internos horiz

	/**
	 * Este devuelve los nombres reales, usar para editar
	 * 
	 * @param workingDir que edificio
	 * @param jc determina que tipo de objetos se crean (editor o node, etc)
	 * @return
	 */
	public static ArrayList<Floor> loadBuilding(File workingDir, JSONConverter jc) {
		return loadBuilding(workingDir, jc, true);
	}

	public static ArrayList<Floor> generateBuilding(File workingDir, JSONConverter jc) {
		ArrayList<Floor> lf = cargarEdificios(workingDir, jc);
		linkearPisos(lf, true);
			for(Floor f: lf){
				calculateHeights(f);
	}
		return lf;
	}
	
	public static ArrayList<Floor> loadBuilding(File workingDir, JSONConverter jc, boolean linked) {
		ArrayList<Floor> lf = cargarEdificios(workingDir, jc);

		if(linked)linkearPisos(lf, true);

		return lf;
	}
	public static void calculateHeights(ArrayList<Floor> lf){
		for(Floor f: lf){
			calculateHeights(f);
		}
	}
	public static void resetHeight(Floor f){
		for (Space s : f.espacios) {
			s.setHeight(null); // reseteo
		}
	}
	
	private static void linkearPisos(ArrayList<Floor> lf, boolean correct) {
		LinkedList<Portal> portalesExternosHorizontal = new LinkedList<Portal>();
		LinkedList<Portal> portalesExternosVertical = new LinkedList<Portal>();
		LinkedList<Integer> portalesExternosVerticalHeight = new LinkedList<Integer>();
		for (Floor f : lf) {
			System.out.println(f.getFloorName());
			for (Portal p : f.getPortales()) {
				if (p.getClase() != Clase.intra_floor) {
					// level*2 +/-1 me permite olvidarme de si sube o baja. Si
					// tienen el mismo level se linkean.
					// el +10 es para que no sea 0
					if (p.getTipo() == Portal.Tipo.ustair) {
						portalesExternosVertical.add(p);
						portalesExternosVerticalHeight.add((f.getLevel()) * 2 + 1);
						System.out.println("agrego al nivel "+ p.getSource().getId() + ((f.getLevel()) * 2 + 1) );
						} else if (p.getTipo() == Portal.Tipo.dstair) {
						portalesExternosVertical.add(p);
						portalesExternosVerticalHeight.add((f.getLevel()) * 2 - 1);
						System.out.println("agrego al nivel__ "+ p.getSource().getId() + (((f.getLevel()) * 2 - 1)));
					} else {
						portalesExternosHorizontal.add(p);
					}
					continue;
				}
			}
		}
		linkPortalesExternosHorizontales(portalesExternosHorizontal, correct);
		linkPortalesExternosVerticales(portalesExternosVertical,portalesExternosVerticalHeight, correct);
	}

	public static ArrayList<Floor> cargarEdificios(File workingDir, JSONConverter jc) {
		ArrayList<Floor> lf = new ArrayList<Floor>();
		String[] archivos = workingDir.list(new FilenameFilter() {
			@Override
			public boolean accept(File dir, String name) {
				return name.endsWith(".json");
			}
		});
		for (String archivo : archivos) {
			Floor f = jc.fromJSONFile(workingDir.getPath() + "/" + archivo);
			// f.setbuildingName(workingDir.getName());
			lf.add(f);
		}
		return lf;
		
	}

	private static void linkPortalesExternosVerticales(
			LinkedList<Portal> portExt, LinkedList<Integer> portHeight, boolean correct) {
		@SuppressWarnings("unchecked")
		LinkedList<Portal> aux = (LinkedList<Portal>) portExt.clone();
		boolean changed;

		while (aux.size() > 1) {
			changed = false;
			Portal p = aux.get(0);
			for (int j = 1; j < aux.size(); j++) {
				Portal p2 = aux.get(j);
				if (portHeight.get(0) == portHeight.get(j)
						&& matchVerticalPortals(p, p2, correct)) {
					p.setTarget(p2.getSource());
					p2.setTarget(p.getSource());
					p.setPtoTarget(p2.getPtoSource());
					p2.setPtoTarget(p.getPtoSource());
					
					aux.remove(j); // p2
					aux.remove(0); // p
					portHeight.remove(j);
					portHeight.remove(0);// ojo con el orden en que se elimina
					// si se elimina al reves, como j>0, hay q eliminar j-1

					changed = true;
					break;
				}
			}
			if (!changed) {
				System.out.println("Hubo problemas al linkear portales externos verticales. Posiblemente no estan correspondidos");
				//break;
				if(aux.size()>0){
					aux.remove(0);
					portHeight.remove(0);
				}
			}
		}
	}

	/*
	 * Los portales matchean si coinciden en 1 de sus puntos (para considerar
	 * escaleras en U, rectas o en L).
	 */
	private static boolean matchVerticalPortals(Portal p1, Portal p2, boolean correct) {
		Point2D p11, p12, p21, p22;
		p11 = p1.getPtoSource();
		p12 = p1.getSource().getNextPoint(p11);
		p21 = p2.getPtoSource();
		p22 = p2.getSource().getNextPoint(p21);
	
	
		if ((p11.distanceSq(p21) < sq_distance_Vertical && p12.distanceSq(p22) < sq_distance_Vertical)) {
			System.out.println("Linking " + p1.getSource().getNombreCompleto()
					+ " con " + p2.getSource().getNombreCompleto());
			// p21.setLocation(p11);
			if(correct){
			p21.setLocation(p11);
			p22.setLocation(p12); // los pego para que se vea pipicucu
			}
			return true;

		}
		if ((p11.distanceSq(p22) < sq_distance_Vertical && p12.distanceSq(p21) < sq_distance_Vertical)) {
			System.out.println("Linking " + p1.getSource().getNombreCompleto()
					+ " con " + p2.getSource().getNombreCompleto());
			if(correct){
			p21.setLocation(p12);
			p22.setLocation(p11);
			}
			return true;
		}
		System.out.println("No se puede linkear "
				+ p1.getSource().getNombreCompleto() + " con "
				+ p2.getSource().getNombreCompleto());

		return false;
	}

	private static void linkPortalesExternosHorizontales(
			LinkedList<Portal> portalesExt, boolean correct) {
		@SuppressWarnings("unchecked")
		LinkedList<Portal> auxiliar = (LinkedList<Portal>) portalesExt.clone();
		boolean changed;
		while (auxiliar.size() > 0) {
			changed = false;
			Portal p = auxiliar.get(0);
			for (Portal p2 : auxiliar) {
				if (p != p2 && matchHorizontalPortals(p, p2)) {
					p.setTarget(p2.getSource());
					p2.setTarget(p.getSource());
					p.setPtoTarget(p2.getPtoSource());
					p2.setPtoTarget(p.getPtoSource());
					auxiliar.remove(p2);
					auxiliar.remove(p);
					changed = true;
					break;
				}
			}
			if (!changed) {
				System.out.println("Hubo problemas al portales." +
						" Posiblemente no estan correspondidos, o muy distanciados");
				if(auxiliar.size()>0) auxiliar.remove(0);
			}
		}
	}
	
	/**
	 * Une los portales non-stairs entre si, y los stairs con un non-stair.
	 **/
	public static void linkInternalPortals(Floor f) {
		System.out.println("Linking internal portals...");
		LinkedList<Portal> unlinkedPortals = new LinkedList<Portal>();
		LinkedList<Portal> unlinkedStairs = new LinkedList<Portal>();
		LinkedList<Portal> candidateStairs = new LinkedList<Portal>(); //pueden llegar a matchear a una escalera
		
		for (Portal p : f.getPortales()) {
			if (p.getClase() == Clase.intra_floor && (p.getTarget() == null)){
				if ((p.getTipo() != Portal.Tipo.ustair && p.getTipo() != Portal.Tipo.dstair))
					unlinkedPortals.add(p);
				else unlinkedStairs.add(p);
			}
		}
		boolean changed;
		System.out.println(unlinkedPortals.size()+" "+unlinkedStairs.size());
		while (unlinkedPortals.size() > 0) {
			changed = false;
			Portal p = unlinkedPortals.get(0);
			for (Portal p2 : unlinkedPortals) {
				if (p != p2 && matchHorizontalPortals(p, p2)) {
					p.setTarget(p2.getSource());
					p2.setTarget(p.getSource());
					p.setPtoTarget(p2.getPtoSource());
					p2.setPtoTarget(p.getPtoSource());
					
					unlinkedPortals.remove(p2);
					unlinkedPortals.remove(p);
					p.setBidirectional(true);
				
					f.getPortales().remove(p2); //remuevo alguo de los dos porque estan repetidos
					changed = true;
					break;
				}
			}
			if (!changed) {
				System.out.println("Falto linkear portales internos. " +
						"Puede ser que est�n huerfanos, o que sean escaleras");
				unlinkedPortals.remove(p);
				candidateStairs.add(p);
			}
		}
		System.out.println("Iniciando linkeo de escaleras internas");
			while (unlinkedStairs.size() > 0) {
				changed = false;
				Portal p = unlinkedStairs.get(0);
				for (Portal p2 : candidateStairs) {
					if (p != p2 && matchStairs(p, p2)) {
						p.setTarget(p2.getSource());
						//p2.setTarget(p.getSource());
						p.setPtoTarget(p2.getPtoSource());
						//p2.setPtoTarget(p.getPtoSource());
						unlinkedStairs.remove(p);
						candidateStairs.remove(p2);
						p.setBidirectional(true);
						//p.setDifferential(10);
						//el tipo ya es stairs
						f.getPortales().remove(p2); //remuevo el candidato porque ya lo linkie
						changed = true;
						break;
					}
				}
				if (!changed) {
					System.out.println("No se encontro fin escalera (puede ser externo).");
					p.setClase(Portal.Clase.inter_floor);
					unlinkedStairs.remove(p);
				}
		}
	}

	
	private static boolean matchStairs(Portal p1, Portal p2) {
		return matchPortal(p1, p2, 20)|| matchPortal(p1, p2, 50)|| matchPortal(p1, p2, 250);
	}

	/**
	 * Los portales matchean (se linkean) si coinciden los puntos que los unen
	 * 
	 * @param p1
	 * @param p2
	 * @return
	 */
	private static boolean matchHorizontalPortals(Portal p1, Portal p2) {
		return matchPortal(p1, p2, distance_horiz);
	}
	
	private static boolean matchPortal(Portal p1, Portal p2, int dif){
		Point2D p11, p12, p21, p22;
		p11 = p1.getPtoSource();
		p12 = p1.getSource().getNextPoint(p11);
		p21 = p2.getPtoSource();
		p22 = p2.getSource().getNextPoint(p21);
		
		float x1, x2, y1, y2;
		//sii es vertical, la distancia en X tiene que se igual
		if(p11.getX()- p12.getX() ==0){
			x1 = (float) (p11.getX() - p21.getX());
			x2 = (float) (p12.getX() - p22.getX());
			if(Math.abs(x1) < dif) //separaci�n m�xima de los portales
			if(Math.abs(x1-x2) < 2){
				y1 = (float) (p11.getY() - p21.getY());
				y2 = (float) (p12.getY() - p22.getY());
				if(Math.abs(y1) <2 && Math.abs(y2)< 2)
					return true;
				y1 = (float) (p11.getY() - p22.getY());
				y2 = (float) (p12.getY() - p21.getY());
				if(Math.abs(y1) <2 && Math.abs(y2)< 2)
					return true;
			}
		}
		//sii es horizontal, la distancia en Y tiene que se igual
		if(p11.getY()- p12.getY() ==0){
			y1 = (float) (p11.getY() - p21.getY());
			y2 = (float) (p12.getY() - p22.getY());
			if(Math.abs(y1) < dif) //separaci�n m�xima de los portales
			if(Math.abs(y1-y2) < 2){
				x1 = (float) (p11.getX() - p21.getX());
				x2 = (float) (p12.getX() - p22.getX());
				if(Math.abs(x1) <2 && Math.abs(x2) <2)
					return true;
				x1 = (float) (p11.getX() - p22.getX());
				x2 = (float) (p12.getX() - p21.getX());
				if(Math.abs(x1) <2 && Math.abs(x2) <2)
					return true;
				}
		}
		/**
		 if ((p11.distanceSq(p21) < 200 && p12.distanceSq(p22) < 200)
		 
				|| (p11.distanceSq(p22) < 200 && p12.distanceSq(p21) < 200)) {
			return true;
		}
		*/
		return false;
	}

	public static void saveSpaceList(LinkedList<String> aulas, File workingDir) {
		FileOutputStream fos;
		OutputStreamWriter out;
		try {
			File f = new File(workingDir.getCanonicalPath() + "/aulas.txt");
			System.out.println(f.getAbsolutePath());
			f.createNewFile();
			fos = new FileOutputStream(f, false);
			out = new OutputStreamWriter(fos, "UTF-8");
			out.write('[');
			int i = aulas.size() - 1;
			for (String s : aulas) {
				out.write("'" + s + "'");
				if (i-- > 0) out.write(", ");
			}
			out.write(']');
			out.close();
		} catch (Exception e) {
			e.printStackTrace();
		}

	}


	public static void calculateHeights(Floor f) {
		if (f.referencia == null) {
			JOptionPane.showMessageDialog(null,
					"Piso " + f.getCompleteFloorName() + " sin referencia. Se elige al azar.");
			if(f.espacios != null && f.espacios.size()>0)
				f.referencia = f.espacios.get(0);
			if(f.referencia == null) 
				return;
		}
	/*	for (Space s : f.espacios) {
			s.setHeight(null); // reseteo
		}*/
		
		int altura = f.alturaAbsouta!=0? f.alturaAbsouta: EditorFloor.ALTURA_NIVEL* f.level+50;
		System.out.println(f.getFloorName() + " "+ f.getLevel() + " la altura es "+ altura);
		f.referencia.setHeight(altura); //establezco la referencia
		System.out.println(f.referencia.id);
		
		
		//System.out.println(f.getReferencia().height);
		// int min = 0;
		int ctrl = 100;
		int cant; //cantidad flotando sin asignar
		do {
			ctrl--; //para que no cicle infinitamente
			cant = 0;
			for (Portal p : f.portales) {
				//System.out.println(p.getSource().height);
				if (p.getClase() != Clase.intra_floor || p.getTarget() == null)
					continue; // no determina altura
				Space src = p.getSource();
				Space tar = p.getTarget(); // no puede ser null porque es intra
				if(tar == null) continue;	// floor.

				int diferencial;
				if (p.getTipo().equals(Tipo.dstair)) {
					diferencial = -p.getDifferential();
				} else if (p.getTipo().equals(Tipo.ustair)) {
					diferencial = p.getDifferential();
				} else
					diferencial = 0;

				if (src.getHeight() == null && tar.getHeight() == null) {
					cant++;
				
					// src.setHeight(0);//dif >0
					// min = Math.min(min, diferencial);
					// tar.setHeight(diferencial);
				} else if (src.getHeight() != null && tar.getHeight() == null) {
					tar.setHeight(src.getHeight() + diferencial);
					// min = Math.min(min, src.getHeight()+diferencial);
				} else if (src.getHeight() == null && tar.getHeight() != null) {
					src.setHeight(tar.getHeight() - diferencial);
					// min = Math.min(min, tar.getHeight() - diferencial);
				} else {
					if (src.getHeight() + diferencial != tar.getHeight())
						System.out.println("error en alturas de "
								+ src.getId() + " con " + tar.getId());
				}
			}
			if(ctrl ==0 ) {System.out.println("existen pisos flotantes"); break;}
		} while (cant != 0);

	}
	
	public static File getWorkingDir() {
		FileInputStream fis;
		InputStreamReader in;
		String s= ".";
		try {
			fis = new FileInputStream("./res.txt"); 

			in = new InputStreamReader(fis, "UTF-8");

			BufferedReader input =  new BufferedReader(in);
			s = input.readLine();
			fis.close();
			in.close();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		JFileChooser jfc = new JFileChooser(s);
		jfc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
		jfc.setVisible(true);
		int returnVal = jfc.showOpenDialog(null);
		if(returnVal == JFileChooser.APPROVE_OPTION){
			FileOutputStream fos;
			OutputStreamWriter out;
			try {
				File f = new File("./res.txt");
				System.out.println(f.getAbsolutePath());
				f.createNewFile();
				fos = new FileOutputStream(f, false);

				out = new OutputStreamWriter(fos, "UTF-8");
				out.write(jfc.getSelectedFile().toString());
				out.close();

			} catch (Exception e) {

				e.printStackTrace();
			} 
		}
		return jfc.getSelectedFile();

	}
	
	public static void saveToFile(String path, String json) {
		try {
			FileWriter writer = new FileWriter(path);
			writer.write(json);
			writer.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	

}
